/******************************************************************************
	Cobalt - utility libraries for building XNA games
	Copyright (C) 2008 Philip Djonov

	This program is free software; you can redistribute it and/or modify it
	under the terms of the GNU General Public License as published by the Free
	Software Foundation; either version 2 of the License, or (at your option)
	any later version.

	This program is distributed in the hope that it will be useful, but
	WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
	or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
	for more details.

	You should have received a copy of the GNU General Public License along
	with this program; if not, write to the
	
		Free Software Foundation, Inc.
		51 Franklin Street, Fifth Floor
		Boston, MA  02110-1301, USA.
******************************************************************************/

using System;
using System.Collections.Generic;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;

namespace Cobalt.Framework.Graphics
{
	internal sealed class MaterialReader : ContentTypeReader<Material>
	{
		private void AssignValue( Effect effect, EffectParameter parameter, object value )
		{
			Type type = value.GetType();

			if( type == typeof( int[] ) )
			{
				parameter.SetValue( (int[])value );
			}
			else if( type == typeof( bool[] ) )
			{
				parameter.SetValue( (bool[])value );
			}
			else if( type == typeof( float[] ) )
			{
				parameter.SetValue( (float[])value );
			}
			else if( type == typeof( Vector2[] ) )
			{
				parameter.SetValue( (Vector2[])value );
			}
			else if( type == typeof( Vector3[] ) )
			{
				parameter.SetValue( (Vector3[])value );
			}
			else if( type == typeof( Vector4[] ) )
			{
				parameter.SetValue( (Vector4[])value );
			}
			else if( type == typeof( Matrix[] ) )
			{
				parameter.SetValue( (Matrix[])value );
			}
			else if( type == typeof( int ) )
			{
				parameter.SetValue( (int)value );
			}
			else if( type == typeof( bool ) )
			{
				parameter.SetValue( (bool)value );
			}
			else if( type == typeof( float ) )
			{
				parameter.SetValue( (float)value );
			}
			else if( type == typeof( Vector2 ) )
			{
				parameter.SetValue( (Vector2)value );
			}
			else if( type == typeof( Vector3 ) )
			{
				parameter.SetValue( (Vector3)value );
			}
			else if( type == typeof( Vector4 ) )
			{
				parameter.SetValue( (Vector4)value );
			}
			else if( type == typeof( Matrix ) )
			{
				parameter.SetValue( (Matrix)value );
			}
			else if( type == typeof( string ) )
			{
				parameter.SetValue( (string)value );
			}
			else
			{
				Texture texture = value as Texture;
				if( texture != null )
				{
					parameter.SetValue( texture );
				}
			}
		}

		private bool ApplyParameters( Effect effect,
			Dictionary<string, object> values )
		{
			bool appliedAny = false;

			foreach( KeyValuePair<string, object> pair in values )
			{
				if( pair.Key == "Technique" )
				{
					EffectTechnique tech = effect.Techniques[(string)pair.Value];
					if( tech != null )
						effect.CurrentTechnique = tech;

					continue;
				}

				EffectParameter parameter = effect.Parameters[pair.Key];
				if( parameter == null )
					continue;

				SharedResource sharedValue = pair.Value as SharedResource;
				if( sharedValue != null )
				{
					sharedValue.GetValue( value => AssignValue( effect, parameter, value ) );
				}
				else
				{
					AssignValue( effect, parameter, pair.Value );
				}

				appliedAny = true;
			}

			return appliedAny;
		}

		private bool HasParameter( Effect effect, IEnumerable<string> names )
		{
			foreach( string name in names )
				if( name == "Technique" || effect.Parameters[name] != null )
					return true;

			return false;
		}

		private Material ReadInlineMaterial( ContentReader input )
		{
			IMaterialService matSvc = input.ContentManager.ServiceProvider.GetService(
				typeof( IMaterialService ) ) as IMaterialService;

			if( matSvc == null )
				throw new InvalidOperationException();

			MaterialManager mm = matSvc.MaterialManager;
			Material ret = new Material( mm );

			ret.Name = input.ReadString();

			Dictionary<string, object> baseProps =
				input.ReadObject<Dictionary<string, object>>();

			for( ; ; )
			{
				int layer = input.ReadInt32();

				if( layer < 0 )
					break;

				LayerShader cloneSource = null;
				Effect effect = input.ReadExternalReference<Effect>();

				Dictionary<string, object> layerProps =
					input.ReadObject<Dictionary<string, object>>();

				if( HasParameter( effect, baseProps.Keys ) ||
					HasParameter( effect, layerProps.Keys ) )
				{
					Effect cloned = effect.Clone( effect.GraphicsDevice );

					ApplyParameters( cloned, baseProps );
					ApplyParameters( cloned, layerProps );

					cloneSource = mm.GetLayer( effect );
					effect = cloned;
				}

				LayerShader layerShader = mm.GetLayer( effect );
				layerShader.UpdateBindings();

				ret.layers[layer] = layerShader;
				ret.layers[layer].cloneSource = cloneSource;
			}

			ret.tags = TagsDictionary.ReadTags( input );

			ret.Loaded();

			return ret;
		}

		private Material ReadNamedMaterial( ContentReader input )
		{
			IMaterialService matSvc = input.ContentManager.ServiceProvider.GetService(
				typeof( IMaterialService ) ) as IMaterialService;

			if( matSvc == null )
				throw new InvalidOperationException();

			return matSvc.MaterialManager.GetMaterial( input.ReadString() );
		}

		protected override Material Read( ContentReader input, Material existingInstance )
		{
			switch( input.ReadInt32() )
			{
			case 0:
				return ReadInlineMaterial( input );

			case 1:
				return input.ReadExternalReference<Material>();

			case 2:
				return ReadNamedMaterial( input );

			default:
				throw new CobaltException( "Invalid material type." );
			}			
		}
	}
}